<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>精灵表帧数据提取工具</title>
    <style>
        body {
            font-family: Arial, sans-serif;
            margin: 20px;
            background-color: #f0f0f0;
        }
        
        .container {
            max-width: 1200px;
            margin: 0 auto;
            background: white;
            padding: 20px;
            border-radius: 8px;
            box-shadow: 0 2px 10px rgba(0,0,0,0.1);
        }
        
        .upload-area {
            border: 2px dashed #ccc;
            padding: 40px;
            text-align: center;
            margin-bottom: 20px;
            border-radius: 8px;
            cursor: pointer;
            transition: border-color 0.3s;
        }
        
        .upload-area:hover {
            border-color: #007bff;
        }
        
        .upload-area.dragover {
            border-color: #007bff;
            background-color: #f8f9fa;
        }
        
        .canvas-container {
            position: relative;
            display: inline-block;
            border: 1px solid #ddd;
            margin-bottom: 20px;
        }
        
        #spriteCanvas {
            cursor: crosshair;
            display: block;
        }
        
        .selection-box {
            position: absolute;
            border: 2px solid #007bff;
            background-color: rgba(0, 123, 255, 0.1);
            pointer-events: none;
        }
        
        .frame-list {
            max-height: 300px;
            overflow-y: auto;
            border: 1px solid #ddd;
            padding: 10px;
            margin-bottom: 20px;
        }
        
        .frame-item {
            display: flex;
            justify-content: space-between;
            align-items: center;
            padding: 8px;
            border-bottom: 1px solid #eee;
            background: #f8f9fa;
            margin-bottom: 5px;
            border-radius: 4px;
        }
        
        .frame-item:hover {
            background: #e9ecef;
        }
        
        .controls {
            display: flex;
            gap: 10px;
            margin-bottom: 20px;
            flex-wrap: wrap;
        }
        
        button {
            padding: 8px 16px;
            border: none;
            border-radius: 4px;
            cursor: pointer;
            font-size: 14px;
        }
        
        .btn-primary {
            background-color: #007bff;
            color: white;
        }
        
        .btn-danger {
            background-color: #dc3545;
            color: white;
        }
        
        .btn-success {
            background-color: #28a745;
            color: white;
        }
        
        .output-area {
            background: #f8f9fa;
            padding: 15px;
            border-radius: 4px;
            border: 1px solid #ddd;
        }
        
        .output-area textarea {
            width: 100%;
            height: 200px;
            font-family: 'Courier New', monospace;
            font-size: 12px;
            border: 1px solid #ccc;
            padding: 10px;
            border-radius: 4px;
        }
        
        .info {
            background: #d1ecf1;
            color: #0c5460;
            padding: 10px;
            border-radius: 4px;
            margin-bottom: 20px;
        }
    </style>
</head>
<body>
    <!-- 
    您想要一个HTML/JS工具来获取不均等分精灵表动画帧的位置和大小。基于之前对话中讨论的弹射物爆炸效果实现需求，我来为您创建一个可视化的精灵表帧数据提取工具。
    ## 精灵表帧数据提取工具
    这个工具可以让您上传精灵表图片，然后通过鼠标点击和拖拽来定义每一帧的位置和大小，最终生成可用于游戏中的帧数据。
    -->
    <div class="container">
        <h1>精灵表帧数据提取工具</h1>
        
        <div class="info">
            <strong>使用说明：</strong>
            <ol>
                <li>上传或拖拽精灵表图片到下方区域</li>
                <li>在图片上拖拽鼠标选择每一帧的区域</li>
                <li>点击"添加帧"按钮保存当前选择</li>
                <li>重复步骤2-3直到所有帧都被定义</li>
                <li>点击"生成代码"获取可用于游戏的帧数据</li>
            </ol>
        </div>
        
        <div class="upload-area" id="uploadArea">
            <p>点击或拖拽图片文件到此处</p>
            <input type="file" id="fileInput" accept="image/*" style="display: none;">
        </div>
        
        <div class="canvas-container" id="canvasContainer" style="display: none;">
            <canvas id="spriteCanvas"></canvas>
            <div class="selection-box" id="selectionBox" style="display: none;"></div>
        </div>
        
        <div class="controls" id="controls" style="display: none;">
            <button class="btn-primary" id="addFrameBtn">添加帧</button>
            <button class="btn-danger" id="clearSelectionBtn">清除选择</button>
            <button class="btn-danger" id="clearAllBtn">清除所有帧</button>
            <button class="btn-success" id="generateCodeBtn">生成代码</button>
        </div>
        
        <div id="frameListContainer" style="display: none;">
            <h3>已定义的帧 (<span id="frameCount">0</span>)</h3>
            <div class="frame-list" id="frameList"></div>
        </div>
        
        <div class="output-area" id="outputArea" style="display: none;">
            <h3>生成的代码</h3>
            <textarea id="outputCode" readonly></textarea>
            <button class="btn-primary" id="copyCodeBtn" style="margin-top: 10px;">复制代码</button>
        </div>
    </div>

    <script>
        class SpriteFrameExtractor {
            constructor() {
                this.canvas = document.getElementById('spriteCanvas');
                this.ctx = this.canvas.getContext('2d');
                this.image = null;
                this.frames = [];
                this.isSelecting = false;
                this.startX = 0;
                this.startY = 0;
                this.currentSelection = null;
                
                this.initEventListeners();
            }
            
            initEventListeners() {
                const uploadArea = document.getElementById('uploadArea');
                const fileInput = document.getElementById('fileInput');
                
                // 文件上传
                uploadArea.addEventListener('click', () => fileInput.click());
                uploadArea.addEventListener('dragover', this.handleDragOver.bind(this));
                uploadArea.addEventListener('drop', this.handleDrop.bind(this));
                fileInput.addEventListener('change', this.handleFileSelect.bind(this));
                
                // 画布事件
                this.canvas.addEventListener('mousedown', this.startSelection.bind(this));
                this.canvas.addEventListener('mousemove', this.updateSelection.bind(this));
                this.canvas.addEventListener('mouseup', this.endSelection.bind(this));
                
                // 控制按钮
                document.getElementById('addFrameBtn').addEventListener('click', this.addFrame.bind(this));
                document.getElementById('clearSelectionBtn').addEventListener('click', this.clearSelection.bind(this));
                document.getElementById('clearAllBtn').addEventListener('click', this.clearAllFrames.bind(this));
                document.getElementById('generateCodeBtn').addEventListener('click', this.generateCode.bind(this));
                document.getElementById('copyCodeBtn').addEventListener('click', this.copyCode.bind(this));
            }
            
            handleDragOver(e) {
                e.preventDefault();
                e.currentTarget.classList.add('dragover');
            }
            
            handleDrop(e) {
                e.preventDefault();
                e.currentTarget.classList.remove('dragover');
                const files = e.dataTransfer.files;
                if (files.length > 0) {
                    this.loadImage(files[0]);
                }
            }
            
            handleFileSelect(e) {
                const file = e.target.files[0];
                if (file) {
                    this.loadImage(file);
                }
            }
            
            loadImage(file) {
                const reader = new FileReader();
                reader.onload = (e) => {
                    const img = new Image();
                    img.onload = () => {
                        this.image = img;
                        this.setupCanvas();
                        this.showControls();
                    };
                    img.src = e.target.result;
                };
                reader.readAsDataURL(file);
            }
            
            setupCanvas() {
                this.canvas.width = this.image.width;
                this.canvas.height = this.image.height;
                this.drawImage();
                
                document.getElementById('canvasContainer').style.display = 'inline-block';
            }
            
            drawImage() {
                this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
                this.ctx.drawImage(this.image, 0, 0);
                
                // 绘制已定义的帧
                this.frames.forEach((frame, index) => {
                    this.ctx.strokeStyle = '#ff0000';
                    this.ctx.lineWidth = 2;
                    this.ctx.strokeRect(frame.x, frame.y, frame.width, frame.height);
                    
                    // 绘制帧编号
                    this.ctx.fillStyle = '#ff0000';
                    this.ctx.font = '14px Arial';
                    this.ctx.fillText(`${index + 1}`, frame.x + 2, frame.y + 16);
                });
            }
            
            startSelection(e) {
                const rect = this.canvas.getBoundingClientRect();
                this.startX = e.clientX - rect.left;
                this.startY = e.clientY - rect.top;
                this.isSelecting = true;
                
                this.clearSelection();
            }
            
            updateSelection(e) {
                if (!this.isSelecting) return;
                
                const rect = this.canvas.getBoundingClientRect();
                const currentX = e.clientX - rect.left;
                const currentY = e.clientY - rect.top;
                
                const x = Math.min(this.startX, currentX);
                const y = Math.min(this.startY, currentY);
                const width = Math.abs(currentX - this.startX);
                const height = Math.abs(currentY - this.startY);
                
                this.currentSelection = { x, y, width, height };
                this.showSelection();
            }
            
            endSelection(e) {
                this.isSelecting = false;
            }
            
            showSelection() {
                const selectionBox = document.getElementById('selectionBox');
                if (this.currentSelection) {
                    selectionBox.style.display = 'block';
                    selectionBox.style.left = this.currentSelection.x + 'px';
                    selectionBox.style.top = this.currentSelection.y + 'px';
                    selectionBox.style.width = this.currentSelection.width + 'px';
                    selectionBox.style.height = this.currentSelection.height + 'px';
                }
            }
            
            clearSelection() {
                document.getElementById('selectionBox').style.display = 'none';
                this.currentSelection = null;
            }
            
            addFrame() {
                if (!this.currentSelection) {
                    alert('请先选择一个区域');
                    return;
                }
                
                const frame = {
                    x: Math.round(this.currentSelection.x),
                    y: Math.round(this.currentSelection.y),
                    width: Math.round(this.currentSelection.width),
                    height: Math.round(this.currentSelection.height)
                };
                
                this.frames.push(frame);
                this.clearSelection();
                this.drawImage();
                this.updateFrameList();
            }
            
            clearAllFrames() {
                if (confirm('确定要清除所有帧吗？')) {
                    this.frames = [];
                    this.clearSelection();
                    this.drawImage();
                    this.updateFrameList();
                }
            }
            
            updateFrameList() {
                const frameList = document.getElementById('frameList');
                const frameCount = document.getElementById('frameCount');
                
                frameCount.textContent = this.frames.length;
                
                frameList.innerHTML = '';
                this.frames.forEach((frame, index) => {
                    const frameItem = document.createElement('div');
                    frameItem.className = 'frame-item';
                    frameItem.innerHTML = `
                        <span>帧 ${index + 1}: x=${frame.x}, y=${frame.y}, w=${frame.width}, h=${frame.height}</span>
                        <button class="btn-danger" onclick="extractor.removeFrame(${index})">删除</button>
                    `;
                    frameList.appendChild(frameItem);
                });
                
                document.getElementById('frameListContainer').style.display = 'block';
            }
            
            removeFrame(index) {
                this.frames.splice(index, 1);
                this.drawImage();
                this.updateFrameList();
            }
            
            generateCode() {
                if (this.frames.length === 0) {
                    alert('请先定义至少一个帧');
                    return;
                }
                
                const code = this.generateFrameCode();
                document.getElementById('outputCode').value = code;
                document.getElementById('outputArea').style.display = 'block';
            }
            
            generateFrameCode() {
                const framesCode = this.frames.map((frame, index) => {
                    return `    { x: ${frame.x}, y: ${frame.y}, width: ${frame.width}, height: ${frame.height} }, // 第${index + 1}帧`;
                }).join('\n');
                
                return `// 精灵表帧数据
const frames = [
${framesCode}
];

/* 
使用示例:
const sprite = new Sprite(imageSource, frames);
*/`;
            }
            
            copyCode() {
                const outputCode = document.getElementById('outputCode');
                outputCode.select();
                document.execCommand('copy');
                alert('代码已复制到剪贴板');
            }
            
            showControls() {
                document.getElementById('controls').style.display = 'flex';
            }
        }
        
        // 创建提取器实例并暴露为全局变量
        const extractor = new SpriteFrameExtractor();
    </script>
</body>    
</html>